package com.ruoyi.framework.netty.childhandler.auth;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.ruoyi.common.constant.heytalk.CommonConstants;
import com.ruoyi.common.constant.heytalk.CommonConstants.ChatTypeEnum;
import com.ruoyi.common.constant.heytalk.utils.EnumUtil;
import com.ruoyi.common.core.domain.heytalk.netty.ChatMsg;
import com.ruoyi.common.core.domain.heytalk.netty.DataType;
import com.ruoyi.common.core.domain.heytalk.netty.UserChannelUtils;
import com.ruoyi.common.core.domain.heytalk.netty.protocol.MsgPacket;
import com.ruoyi.common.core.domain.heytalk.vo.LoginImUser;
import com.ruoyi.common.core.service.HeyTalkTokenService;
import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.heytalk.ByteBufUtils;
import com.ruoyi.common.utils.heytalk.MapUrlParamsUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.netty.properties.NettyChatProperties;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;

@Slf4j
@ChannelHandler.Sharable
public class AuthStrategyHandler extends SimpleChannelInboundHandler<Object> {

    /**
     * 提供单例调用方式
     */
    public static final AuthStrategyHandler getInstance = new AuthStrategyHandler();

    private WebSocketServerHandshaker webSocketServerHandshaker;

    /**
     * 通道进入后会进入通道读取方法
     * @param ctx 通道处理上下文
     * @param msg 消息
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("有事件进入了");
        if (msg instanceof FullHttpRequest) {
            handlerHttpRequest(ctx, (FullHttpRequest) msg);
            System.out.println("完成握手和相关用户校验");
        } else if (msg instanceof WebSocketFrame) {
            try {
                handlerWebSocketFrame(ctx, (WebSocketFrame) msg);
            } catch (Exception e) {
                log.error("报错了:{}", e);
                throw new Exception();
            }
        }
    }

    private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame webSocketFrame) {
        //判断客户端是否执行了WebSocket的关闭指令
        if (webSocketFrame instanceof CloseWebSocketFrame) {
            System.out.println("客户端执行了websocket关闭命令");
            //解绑
            UserChannelUtils.unBindUser(ctx.channel());
            this.webSocketServerHandshaker.close(ctx.channel(), ((CloseWebSocketFrame) webSocketFrame).retain());
            return;
        }
        // 判断是否Ping消息
        if (webSocketFrame instanceof PingWebSocketFrame) {
            log.info("ping message:{}", webSocketFrame.content().retain());
            ctx.writeAndFlush(new PongWebSocketFrame(webSocketFrame.content().retain()));
            return;
        }
        // 判断是否Pong消息
        if (webSocketFrame instanceof PongWebSocketFrame) {
            log.info("pong message:{}", webSocketFrame.content().retain());
            ctx.writeAndFlush(new PongWebSocketFrame(webSocketFrame.content().retain()));
            return;
        }
        //这里只操作文本类webSocket帧消息
        if (webSocketFrame instanceof TextWebSocketFrame) {
            TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) webSocketFrame;
            String content = textWebSocketFrame.content().toString(StandardCharsets.UTF_8);
            log.info("客户端发送content:{}", content);
            MsgPacket msgPacket = JsonUtils.parseObject(content, MsgPacket.class);
            log.info("客户端发送msgPacket:{}", msgPacket);
            if (Objects.isNull(msgPacket) || StrUtil.isBlankOrUndefined(msgPacket.getChatType())) {
                ctx.writeAndFlush(ByteBufUtils.getByteBuf(DataType.builder()
                        .type(CommonConstants.DataTypeEnum.TIPS.getCode())
                        .msg("发送的数据不合法").build()));
            }
//            switch (EnumUtil.getEnumObjectByCode(msgPacket.getChatType(), ChatTypeEnum.class)) {
//                case SINGLE_CHAT:
//                    ChatMsg chatMsg = JsonUtils.parseObject(content, ChatMsg.class);
//                    System.out.println("chatMsg===" +chatMsg.toString());
//                    msgPacket = chatMsg;
//                    break;
//                case GROUP_CHAT:
//                    System.out.println("进入了群聊");
//                    break;
//                default:
//                    break;
//            }
            ctx.fireChannelRead(msgPacket);
        }
    }

    /**
     * 1、处理http请求，完成http协议到websocket协议的升级
     * 2、处理客户端链接时用户权限校验
     * @param ctx 通道处理上下文
     * @param fullHttpRequest 完整的http请求
     */
    private void handlerHttpRequest(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) {
        if (!fullHttpRequest.decoderResult().isSuccess()) {
            sendHttpResponse(ctx, fullHttpRequest,
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        WebSocketServerHandshakerFactory handshakerFactory = new WebSocketServerHandshakerFactory(
                CommonConstants.WS_PROTOCOL + ctx.channel() +
                        SpringUtil.getBean(NettyChatProperties.class).getWebsocketUrl(), null, false);
        WebSocketServerHandshaker handshaker = handshakerFactory.newHandshaker(fullHttpRequest);
        this.webSocketServerHandshaker = handshaker;
        if (Objects.isNull(handshaker)) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            //动态加入websocket的编解码处理
            handshaker.handshake(ctx.channel(), fullHttpRequest);
            if (!UserChannelUtils.hasLogin(ctx.channel())) {
                //处理用户权限信息校验
                AuthUserHandler(ctx, fullHttpRequest);
            }
        }
    }

    /**
     * 校验用户
     * @param ctx 通道处理上下文
     * @param fullHttpRequest 完整的http请求
     */
    private void AuthUserHandler(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) {
        System.out.println("进入登录流程校验");
        Map<String, String> uriMap = MapUrlParamsUtils.getUrlParams(fullHttpRequest.uri());
        String token = uriMap.get(SpringUtil.getBean(NettyChatProperties.class).getWebsocketUrl() + "?token");
        if (StringUtils.isEmpty(token)) {
            return;
        }
        LoginImUser loginImUser = SpringUtils.getBean(HeyTalkTokenService.class).getLoginImUserFromToken(token);
        if (Objects.isNull(loginImUser)) {
            ctx.channel().writeAndFlush(
                    ByteBufUtils.getByteBuf(DataType.builder()
                            .type(CommonConstants.DataTypeEnum.TIPS.getCode())
                            .msg("用户数据不存在").build()));
            ctx.channel().close();
            return;
        }
        if (Objects.equals(CommonConstants.UserStatusEnum.NORMAL.getCode(), loginImUser.getUser().getStatus())) {
            ctx.channel().writeAndFlush(
                    ByteBufUtils.getByteBuf(DataType.builder()
                            .type(CommonConstants.DataTypeEnum.TIPS.getCode())
                            .msg(MessageUtils.message("heyTalk.user.disable", loginImUser.getUser().getUserName())).build()));
            ctx.channel().close();
            return;
        }
        Channel channel = UserChannelUtils.getChannel(loginImUser.getUserId());
        //有可能客户端刷新了webSocket,也会导致ctx.channel()改变。说明之前绑定了
        if (!Objects.isNull(channel)) {
            UserChannelUtils.unBindUser(channel);
        }
        UserChannelUtils.bindUser(loginImUser, ctx.channel());
        System.out.println("已绑定ctx.channel()===" + ctx.channel());
    }

    /**
     * 发送http相应
     * @param ctx 通道上下文
     * @param req 完整的http请求
     * @param res 默认的http相应
     */
    private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res) {
        // 返回应答给客户端
        if (res.status().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
        }
        // 如果是非Keep-Alive，关闭连接
        boolean keepAlive = HttpUtil.isKeepAlive(req);
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if (!keepAlive) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

    // 异常处理，netty默认是关闭channel
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        UserChannelUtils.unBindUser(ctx.channel());
        ctx.close();
    }

}
